package game;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

import stopwatch.StopWatch;
import tree.*;

/**
 * Game class - 24-Game.
 * @author Natchanon Hongladaromp
 *
 */
public class Game extends Observable
{	
	private GameMode mode;
	private List<Task> tasks;
	private int currentRound;
	
	private List<Tree> currentTrees;
	
	private StopWatch watch;
	
	private boolean finish;

	/**
	 * Construct for this game.
	 */
	public Game()
	{
		tasks = new ArrayList<Task>();
		watch = new StopWatch();
		currentTrees = new ArrayList<Tree>();
		mode = new ClassicMode(this,5);
	}
	
	/**
	 * Sets mode
	 * @param mode mode to set.
	 */
	public void setMode(GameMode mode)
	{
		this.mode = mode;
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_SCORE);
	}
	
	/**
	 * Starts this game.
	 */
	public void start()
	{
		tasks.clear();
		watch.start();
		currentRound = 0;
		finish = false;
		
		newTask();
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_NUMBER);
		notifyObservers(UpdateType.UPDATE_TIME);
	}
	
	/**
	 * Creates new task.
	 */
	public void newTask()
	{
		tasks.add(TaskFactory.createTask());
		
		currentTrees.clear();
		for(int n: tasks.get(currentRound).getNumbers())
		{
			currentTrees.add(new NumberNode(n));
		}
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_NUMBER);
	}
	
	/**
	 * Returns current round.
	 * @return current round
	 */
	public int getRound()
	{
		return currentRound+1;
	}
	
	/**
	 * Returns time.
	 * @return time(second)
	 */
	public double getTime()
	{
		return watch.getElapsed();
	}
	
	/**
	 * Calculate numbers in this game.
	 * @param index1 index of first number
	 * @param index2 index of second number
	 * @param operation operation
	 * @return true if can calculate
	 */
	public boolean calculate(int index1, int index2, String operation)
	{
		int temp = currentTrees.size();
		if(index1 != index2 && index1 >= temp && index2 >= temp)
		{
			return false;
		}
		
		Tree newTree;
		Tree tree1 = currentTrees.get(index1);
		Tree tree2 = currentTrees.get(index2);
		
		switch(operation)
		{
		case "+":
			newTree = new AddNode(tree1,tree2);
			break;
		case "-":
			newTree = new SubtractNode(tree1,tree2);
			break;
		case "*":
			newTree = new MultiplyNode(tree1,tree2);
			break;
		case "/":
			if(tree2.value() == 0)
				return false;
			newTree = new DivideNode(tree1,tree2);
			break;
			default:
				return false;
		}
		
		currentTrees.remove(tree1);
		currentTrees.remove(tree2);
		currentTrees.add(newTree);
		
		if(currentTrees.size() == 1)
		{
			if(currentTrees.get(0).value() == 24)
			{
				currentRound++;
				mode.newTask();
			}
			else
				reset();
		}
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_NUMBER);
		
		return true;
	}
	
	/**
	 * Resets current task.
	 */
	public void reset()
	{
		int[] numbers = tasks.get(currentRound).getNumbers();
		
		currentTrees.clear();
		for (int n : numbers)
		{
			currentTrees.add(new NumberNode(n));
		}
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_NUMBER);
	}
	
	/**
	 * Updates time.
	 */
	public void updateTime()
	{
		mode.updateTime();
		setChanged();
		notifyObservers(UpdateType.UPDATE_TIME);
	
	}

	/**
	 * Checks this game is finish.
	 * @return true if this game is finish
	 */
	public boolean isFinish()
	{
		return finish;
	}

	/**
	 * Returns list of numbers.
	 * @return list of numbers
	 */
	public List<Tree> getTree()
	{
		return currentTrees;
	}
	
	/**
	 * Returns score.
	 * @return score
	 */
	public double getScore()
	{
		return mode.getScore();
	}
	
	/**
	 * Ends this game.
	 */
	public void end()
	{
		currentTrees.clear();
		watch.stop();
		finish = true;
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_NUMBER);
		setChanged();
		notifyObservers(UpdateType.UPDATE_END);
		
		
	}
	
	/**
	 * Pass currenct task.
	 */
	public void pass()
	{
		tasks.remove(currentRound);
		tasks.add(TaskFactory.createTask());
		
		currentTrees.clear();
		for(int n: tasks.get(currentRound).getNumbers())
		{
			currentTrees.add(new NumberNode(n));
		}
		
		setChanged();
		notifyObservers(UpdateType.UPDATE_NUMBER);
	}
	
	/**
	 * Returns hint of this task.
	 * @return hint of this task
	 */
	public String getHint()
	{
		return tasks.get(currentRound).getSolution();
	}

	/**
	 * Returns mode.
	 * @return mode
	 */
	public GameMode getMode()
	{
		return mode;
	}
	
}
